/*
Library for:
 * general networking
 * sockets, pipes, etc.
 * unbuffered I/O
 * other items relating to the above

by John Goerzen, Linux Programming Bible
modified by Stephen Schaub
*/

#include <ctype.h>
#include <stdlib.h>
#include "networkinglib.h"

static int checkstring(const char *string);

//-------------------------------------------------
// int clientconnect(const char *host, const char *port, const char *proto)
// Purpose:
//     Creates a client socket found at inet address <host> on port <port> using protocol <proto>
//     and attempts to connect() to the client.
// Parameters:
//     host:    the IP address of the host
//     port:    the connection port number
//     proto:    the connection protocol
// Returns:
//     on success, the socket id
//     on error, -1
// Typical Use:
//     int sockid = clientconnect("192.168.5.1", "21", "tcp");
//     if (sockid < 0) {
//       // connect failed
//     } else {
//       // connect succeeded
//     }
//-------------------------------------------------
int clientconnect(const char *host, const char *port, const char *proto) {
  struct sockaddr_in socketaddr;
  int sockid = -1;

  socketaddr_init(&socketaddr);
  if (socketaddr_service(&socketaddr, port, proto) < 0) return -1;
  if (socketaddr_host(&socketaddr, host) < 0) return -1;

  sockid = socket(PF_INET, prototype(proto), resolveproto(proto));
  if (sockid < 0) {
    HandleError(errno, "clientconnect", "socket failed");
    return -1;
  }

  if (connect(sockid, (struct sockaddr *) &socketaddr, sizeof(socketaddr)) < 0) {
    HandleError(errno, "clientconnect", "connect failed");
    return -1;
  }

  return sockid;
}


//-------------------------------------------------
// int serverinit(const char *port, const char *proto)
// Purpose:
//     Creates a server socket on port "port" using
//     protocol "proto" and attempts to bind() & listen()
//     for incoming connections.
// Parameters:
//     const char *port:    the port number to listen on
//     const char *proto:    the protocol to use
// Example:
//     int sockid = serverinit("21", "tcp");
// Returns:
//     on success, the socket id
//     on error, -1
//-------------------------------------------------
int serverinit(const char *port, const char *proto) {
  struct sockaddr_in socketaddr;
  int mastersock;
  int trueval = 1;

  socketaddr_init(&socketaddr);
  socketaddr.sin_addr.s_addr = INADDR_ANY;
  socketaddr_service(&socketaddr, port, proto);

  mastersock = socket(PF_INET, prototype(proto), resolveproto(proto));
  if (mastersock < 0) {
    HandleError(errno, "serverinit", "couldn't create socket");
    return -1;
  }

  if (bind(mastersock, (struct sockaddr *) &socketaddr, sizeof(socketaddr)) < 0) {
    HandleError(errno, "serverinit", "bind to port %d failed",
                ntohs(socketaddr.sin_port));
    return -1;
  }

  setsockopt(mastersock, SOL_SOCKET, SO_REUSEADDR, &trueval, sizeof(trueval));

  if (prototype(proto) == SOCK_STREAM) {
    if (listen(mastersock, 5) < 0) {
      HandleError(errno, "serverinit", "listen on port %d failed",
                  socketaddr.sin_port);
      return -1;
    }
  }

  return mastersock;
}


/* Removes CR and LF from the end of a string. */
void stripcrlf(char *temp)
{
  while (strlen(temp) &&
         ((temp[strlen(temp)-1] == 13) || (temp[strlen(temp)-1] == 10))) {
    temp[strlen(temp)-1] = 0;
  }
}

//-------------------------------------------------
// int writestring(int sockid, char *str)
// Purpose:
//     Sends a text string to a socket.  The string
//     MUST be null-terminated, and the null will
//     be sent with the string.
// Parameters:
//     sockid:    the socket to write to
//     str:    the null-terminated string
// Returns:
//     on success, the number of bytes sent
//     on error, the error code (< 0)
//-------------------------------------------------
int writestring(int sockid, char *str) {
  return write_buffer(sockid, str, strlen(str) + 1);
}

/* reads a string terminated by a NULL */
int readstring(int sockid, char *buf, int maxlen) {
  return readdelimstring(sockid, buf, maxlen, 0);
}

/* Reads a string terminated by a newline */
int readnlstring(int sockid, char *buf, int maxlen) {
  return readdelimstring(sockid, buf, maxlen, '\n');
}

// int readdelimstring(int sockid, char *buf, int maxlen, char delim)
// Purpose:
//     Reads a text string from a socket.  Stops reading at either maxlen-1 bytes or the
//     specified delimiter. The delimiter is not included in the string. If maxlen-1
//     bytes are read without encountering a delimiter, null terminates the buffer and
//     exits. If EOF is encountered while reading, signals an error and returns 0.
// Parameters:
//     sockid:  the socket to read from
//     buf:     the string to write to
//     maxlen:  maximum number of bytes to read (not counting null terminator)
//     delim:   the delimiter
// Returns:
//     on success, the number of bytes read
//     on error, the error code (< 0)
//
// Typical Usage:
//
//    char buf[80];
//    int numBytes;
//
//    numBytes = readdelimstring(socket, buf, sizeof(buf), '\n');
//    if (numBytes < 0) {
//      // error
//    } else if (numBytes < sizeof(buf)-1) {
//      // got delimiter
//    } else {
//      // buffer filled without encountering delimiter
//    }
//-------------------------------------------------
int readdelimstring(int sockid, char *buf, int maxlen, char delim) {
  int count = 0, status;

  while (count < maxlen-1) {
    status = saferead(sockid, buf+count, 1);
    if (status < 0) return status;
    if (status < 1) {
      HandleError(0, "readdelimstring", "unexpected EOF from socket");
      return status;
    }
    if (buf[count] == delim) {            /* Found the delimiter */
      break;
    }
    count++;
  }
  buf[count] = 0;
  return count;
}

/* Copies data from the in to the out file descriptor.  If numsize
   is nonzero, specifies the maximum number of bytes to copy.  If
   it is 0, data will continue being copied until in returns EOF. */

int copy(int in, int out, unsigned long maxbytes) {
  char buffer[COPY_BUFSIZE];
  int indata, remaining;

  remaining = maxbytes;

  while (remaining || !maxbytes) {
    indata = saferead(in, buffer,
             (!remaining || COPY_BUFSIZE < remaining) ? COPY_BUFSIZE
              : remaining);
    if (indata < 1) return indata;
    write_buffer(out, buffer, indata);
    if (maxbytes) remaining -= indata;
  }
  return (0);
}


/*
   This function will write a certain number of bytes from the buffer
   to the descriptor fd.  The number of bytes written are returned.
   This function will not return until all data is written or an error
   occurs.
*/

int write_buffer(int fd, char *buf, int count) {
  int  status = 0, result;

  if (count < 0) return (-1);
 
  while (status != count) {
    result = safewrite(fd, buf + status, count - status);
    if (result < 0) return result;
    status += result;
  }
  return (status);
}

//-------------------------------------------------
// int read_buffer(int fd, char *buf, int count)
// Purpose:
//     Reads a number of bytes from a socket (or
//     file descriptor).  Stops reading at either
//     <count> bytes or EOF.  Makes multiple successive
//     attempts to read.
// Parameters:
//     fd:        the file descriptor to read from
//     buf:    the string to write to
//     count:    max number of bytes to read
// Returns:
//         on success, the number of bytes read
//         on error, the error code (< 0)
//-------------------------------------------------
int read_buffer(int fd, char *buf, int count) {
  char *pts = buf;
  int  status = 0, n;

  if (count < 0) return (-1);

  while (status != count) {
    n = saferead(fd, pts+status, count-status);
    if (n < 0) return n;
    if (n == 0) return status;
    status += n;
  }
  return (status);
}

/* Reads a uint32 from the network in network byte order.

   A note on the implementation: because some architectures cannot
   write to the memory of the integer except all at once, a character
   buffer is used that is then copied into place all at once. */

int read_netulong(int fd, uint32_t *value) {
  char buffer[sizeof(uint32_t)];
  int status;

  status = read_buffer(fd, buffer, sizeof(uint32_t));
  if (status != sizeof(uint32_t)) {
    HandleError(0, "read_netulong", "unexpected EOF");
    return -1;
  }
  bcopy(buffer, (char *)value, sizeof(uint32_t));
  *value = ntohl(*value);
  return (0);
}

/* Write an unsigned long in network byte order */

int write_netulong(int fd, const unsigned long int value) {
  char buffer[sizeof(uint32_t)];
  uint32_t temp;
  int status;

   temp = htonl(value);
   bcopy((char *)&temp, buffer, sizeof(temp));
   status = write_buffer(fd, buffer, sizeof(temp));
   if (status != sizeof(temp)) return -1;
   return (0);
}

/* Returns the fully qualified domain name of the current host. */
char *getmyfqdn(void) {
  char hostname[200];
  gethostname(hostname, sizeof(hostname));
  return getfqdn(hostname);
}

/* Returns the fully qualified domain name of an arbitrary host. */
char *getfqdn(const char *host) {
  struct hostent *hp;
  static char fqdn[200];

  hp = gethostbyname(host);
  if (!hp)
    return (char *)NULL;
  strlcpy(fqdn, (hp->h_aliases[0]) ? hp->h_aliases[0] : hp->h_name,
              sizeof(fqdn));
  return fqdn;
}

void socketaddr_init(struct sockaddr_in *socketaddr) {
  bzero((char *) socketaddr, sizeof(*socketaddr));
  socketaddr->sin_family = AF_INET;
}

int socketaddr_service(struct sockaddr_in *socketaddr,
                       const char *service, const char *proto) {
  struct servent *serviceaddr;

  /* Need to allow numeric as well as textual data. */

  /* 0: pass right through. */

  if (strcmp(service, "0") == 0)
    socketaddr->sin_port = 0;
  else {                           /* nonzero port */
    serviceaddr = getservbyname(service, proto);
    if (serviceaddr) {
      socketaddr->sin_port = serviceaddr->s_port;
    } else {                      /* name did not resolve, try number */
      if (checkstring(service)) { /* and it's a text name, fail. */
        HandleError(0, "socketaddr_service", "no lookup for %s/%s",
                    service, proto);
        return -1;
      }
      if ((socketaddr->sin_port = htons((u_short)atoi(service))) == 0) {
        HandleError(0, "socketaddr_service", "numeric conversion failed");
        return -1;
      }
    }
  }

  return 0;
}

int socketaddr_host(struct sockaddr_in *socketaddr,
                    const char *host) {
  struct hostent *hostaddr;
  hostaddr = gethostbyname(host);
  if (!hostaddr) {
    HandleError(0, "socketaddr_host", "gethostbyname failed for %s", host);
    return -1;
  }

  memcpy(&socketaddr->sin_addr, hostaddr->h_addr, hostaddr->h_length);
  return 0;
}

int resolveproto(const char *proto) {
  struct protoent *protocol;
  protocol = getprotobyname(proto);
  if (!protocol) {
    HandleError(0, "resolveproto", "getprotobyname failed for proto = %s", proto);
    return -1;
  }

  return protocol->p_proto;
}

int prototype(const char *proto) {
  if (strcmp(proto, "tcp") == 0) return SOCK_STREAM;
  if (strcmp(proto, "udp") == 0) return SOCK_DGRAM;
  HandleError(0, "prototype", "prototype failed for proto = %s", proto);
  return -1;
}




/* checkstring() is a private function used only by this library.  It checks
    the passed string.  It returns false if there are no nonnumeric
    characters  in the string, or true if there are such characters. */

static int checkstring(const char *string) {
int counter;
  for (counter = 0; counter < strlen(string); counter++)
    if (!(isdigit(string[counter])))
      return 1;
  return 0;
}
